﻿using System.Reflection;
using ApiConceptsFramework.FluentApi;
using NUnit.Framework;
using DIApi.Methods;
using test.ApiConceptsFramework.FluentApi.Scenarios.TestClasses;

namespace test.ApiConceptsFramework.FluentApi.Scenarios
{
	[TestFixture, Explicit]
	public class DiApiFluentTests
	{
		private Assembly[] _assemblies;
		private string[] _usings;
		private ApiEvaluator _eval;

		#region Setup
		[SetUp]
		public void SetUp()
		{
			_assemblies = new[] { typeof(DIContainer).Assembly };
			_usings = new[] { "DIApi.Methods" };

			_eval = new ApiEvaluator(_assemblies);
			_eval.SetUsings(_usings);
			_eval.SetAssemblyRefs(_assemblies);
		}

		[TearDown]
		public void TearDown()
		{
			_eval.EvaluateAndPrintToConsole();
		}
		#endregion

		[Test]
		public void Simple_Binding()
		{
			//var container = new DIContainer();
			//container.Bind<IFoo>().To<Foo>();
			//var instance = container.Get<IFoo>();

			//_eval.ClassUsage<DIContainer>();
			_eval.Instantiation(() => new DIContainer());
			_eval.MethodCall((DIContainer c) => c.Bind<IFoo>());
			_eval.MethodCall((FluentConfigAfterBind<IFoo> c) => c.To<Foo>());
			_eval.FluentMethodChain((DIContainer c) => c.Bind<IFoo>().To<Foo>());
			_eval.MethodCall((DIContainer c) => c.Get<IFoo>());
		}

		[Test]
		public void Named_Bindings()
		{
			//var container = new DIContainer();
			//container.Bind<IFoo>().To<Foo>().Named("t1");
			//container.Bind<IFoo>().To<Foo2>().Named("t2");
			//var instance = container.Get<IFoo>();

			//_eval.ClassUsage<DIContainer>(1,1);
			_eval.Instantiation(() => new DIContainer(),1,1);
			_eval.MethodCall((DIContainer c) => c.Bind<IFoo>(), 2, 1);
			_eval.MethodCall((FluentConfigAfterBind<IFoo> c) => c.To<Foo>(), 2, 1);
			_eval.MethodCall((FluentConfigAfterTo<IFoo> c) => c.Named("name"), 2);
			_eval.FluentMethodChain((DIContainer c) => c.Bind<IFoo>().To<Foo>().Named("name"), 2);
			_eval.MethodCall((DIContainer c) => c.Get<IFoo>("name"), 2); //overload with other params - so no previous usages!!
		}

		[Test]
		public void Constructor_Injection_simple()
		{
			//var container = new DIContainer();
			//container.Bind<IFoo>().To<Foo>();
			//container.WhenInstantiating<Service>().UseConstructorWithTypes<IFoo>();
			//var instance = container.Get<Service>();

			//_eval.ClassUsage<DIContainer>(1, 2); //for a "new" use case, the usages could be reset...?
			////_eval.ClassUsage<FluentConfigAfterInstantiating<Service>>();
			_eval.Instantiation(() => new DIContainer(), 1, 2);
			_eval.MethodCall((DIContainer c) => c.Bind<IFoo>(), 1, 3);
			_eval.MethodCall((FluentConfigAfterBind<IFoo> c) => c.To<Foo>(), 1, 3);
			_eval.FluentMethodChain((DIContainer c) => c.Bind<IFoo>().To<Foo>(), 1, 1);
			_eval.MethodCall((DIContainer c) => c.WhenInstantiating<Service>());
			_eval.MethodCall((FluentConfigAfterInstantiating<Service> c) => c.UseConstructorWithTypes<IFoo>()).RequiresInformationLookup();
			_eval.FluentMethodChain((DIContainer c) => c.WhenInstantiating<Service>().UseConstructorWithTypes<IFoo>());
			_eval.MethodCall((DIContainer c) => c.Get<Service>(), 1, 1);
		}

		[Test]
		public void Constructor_Injection_complex()
		{
			//var container = new DIContainer();
			//container.Bind<IFoo>().To<Foo>().Named("t1");
			//container.Bind<IFoo>().To<Foo2>().Named("t2");
			//container.WhenInstantiating<Service>()
			//	.ForConstructorParam("p1").Inject("t1");
			//	.ForConstructorParam("p2").Inject("t2");
			//var instance = container.Get<Service>();

			//_eval.ClassUsage<DIContainer>(1, 3);
			_eval.Instantiation(() => new DIContainer(), 1, 3);

			_eval.MethodCall((DIContainer c) => c.Bind<IFoo>(), 2, 4);
			_eval.MethodCall((FluentConfigAfterBind<IFoo> c) => c.To<Foo>(), 2, 4);
			_eval.MethodCall((FluentConfigAfterTo<IFoo> c) => c.Named("name"), 2, 2);
			_eval.FluentMethodChain((DIContainer c) => c.Bind<IFoo>().To<Foo>().Named("name"), 2, 2);

			_eval.MethodCall((DIContainer c) => c.WhenInstantiating<Service>(), 1, 1);
			//_eval.MethodCall((FluentConfigAfterInstantiating<Service> c) => c.UseConstructorWithTypes<IFoo>(), 1, );
			_eval.MethodCall((FluentConfigAfterInstantiating<Service> c) => c.ForConstructorParam("param"), 2).RequiresInformationLookup();
			_eval.MethodCall((FluentConfigAfterForParam<Service> c) => c.Inject("binding"), 2);
			_eval.FluentMethodChain((DIContainer c) => c.WhenInstantiating<Service>() //.UseConstructorWithTypes<IFoo>()
				.ForConstructorParam("param1").Inject("binding1")
				.ForConstructorParam("param2").Inject("binding2"));
			_eval.MethodCall((DIContainer c) => c.Get<Service>(), 1, 2);

			//_eval.PrintConceptEvaluationDetails();
		}

		[Test]
		public void Binding_with_singleton_scope()
		{
			//var container = new DIContainer();
			//container.Bind<IFoo>().To<Foo>().InSingletonScope();
			//var instance = container.Get<IFoo>();

			//_eval.ClassUsage<DIContainer>(1, 4);
			_eval.Instantiation(() => new DIContainer(), 1, 4);
			_eval.MethodCall((DIContainer c) => c.Bind<IFoo>(), 1, 5);
			_eval.MethodCall((FluentConfigAfterBind<IFoo> c) => c.To<Foo>(), 1, 5);
			_eval.MethodCall((FluentConfigAfterTo<IFoo> c) => c.InSingletonScope());
			_eval.FluentMethodChain((DIContainer c) => c.Bind<IFoo>().To<Foo>().InSingletonScope());
			_eval.MethodCall((DIContainer c) => c.Get<IFoo>(), 2, 3);
		}

		[Test]
		public void All()
		{
			//var container = new DIContainer();
			//container.Bind<ILogger>().To<ConsoleLogger>().Named("console").InSingletonScope();
			//container.Bind<ILogger>().To<FileLogger>().Named("file");
			//container.Bind<IAuthenticator>().To<WindowsBasedAuthenticator>().Named("auth");
			//container.WhenInstantiating<Service>()
			//  .UseConstructorWithTypes<ILogger,ILogger,IAuthenticator>()
			//	.ForConstructorParam("consoleLogger").Inject("console");
			//	.ForConstructorParam("fileLogger").Inject("file");
			//	.ForConstructorParam("auth").Inject("auth");
			//container.WhenInstantiating<WindowsBasedAuthenticator>()
			//	.ForConstructorParam("logger").Inject("console");
			//var instance = container.Get<Service>();

			//_eval.ClassUsage<DIContainer>(1, 5);
			_eval.Instantiation(() => new DIContainer(), 1, 5);

			_eval.MethodCall((DIContainer c) => c.Bind<IFoo>(), 3, 6);
			_eval.MethodCall((FluentConfigAfterBind<IFoo> c) => c.To<Foo>(), 3, 6);
			_eval.MethodCall((FluentConfigAfterTo<IFoo> c) => c.Named("name"), 3, 2);
			_eval.MethodCall((FluentConfigAfterTo<IFoo> c) => c.InSingletonScope(), 1, 1);
			_eval.FluentMethodChain((DIContainer c) => c.Bind<IFoo>().To<Foo>().Named("name"), 3, 4);

			_eval.MethodCall((DIContainer c) => c.WhenInstantiating<Service>(), 2, 2);
			_eval.MethodCall((FluentConfigAfterInstantiating<Service> c) => c.UseConstructorWithTypes<IFoo, IFoo, IFoo>(), 1, 2).RequiresInformationLookups(3);
			_eval.MethodCall((FluentConfigAfterInstantiating<Service> c) => c.ForConstructorParam("param"), 4, 2).RequiresInformationLookup();
			_eval.MethodCall((FluentConfigAfterForParam<Service> c) => c.Inject("binding"), 4, 2);
			_eval.FluentMethodChain((DIContainer c) => c.WhenInstantiating<Service>()
				.UseConstructorWithTypes<IFoo, IFoo, IFoo>()
				.ForConstructorParam("param1").Inject("binding1")
				.ForConstructorParam("param2").Inject("binding2")
				.ForConstructorParam("param3").Inject("binding3"), 1, 1);

			_eval.MethodCall((DIContainer c) => c.Get<Service>(), 1, 2);
		}
	}
}
